package net.ncraft.mc.easyframework.context.core;

import net.ncraft.mc.easyframework.EasyRule;
import net.ncraft.mc.easyframework.context.ApplicationContext;
import net.ncraft.mc.easyframework.context.anno.Autowired;
import net.ncraft.mc.easyframework.context.anno.Bean;
import net.ncraft.mc.easyframework.context.anno.Component;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class DefaultApplicationContext implements ApplicationContext {

    private final List<Class<?>> COMPONENTS = new ArrayList<>();
    private final Set<Object> BEANS = new LinkedHashSet<>();
    private final Map<String, Object> NAME_BEANS = new HashMap<>();
    private final Map<String, Object> CLASSNAME_BEANS = new HashMap<>();
    private final Map<Class<?>, Object> TYPE_BEANS = new HashMap<>();
    private final Map<Class<?>, Object> INTERFACE_BEANS = new HashMap<>();


    @Override
    public List<Class<?>> getComponents() {
        return COMPONENTS;
    }

    @Override
    public void init() {
        instantiateBeans();
    }

    @Override
    public void initExplicitBeans() {
        registerBeans();
    }

    @Override
    public void scan(String basePackage) {
        Reflections r = new Reflections(basePackage);
        Set<Class<?>> allClasses = r.getTypesAnnotatedWith(Component.class);
        scan(allClasses);
    }

    @Override
    public void scan(String basePackage, String exclusion) {
        Reflections scanPackage = new Reflections(basePackage);
        Reflections exclusionPackage = new Reflections(exclusion);
        Set<Class<?>> allClasses = scanPackage.getTypesAnnotatedWith(Component.class);
        allClasses.removeAll(exclusionPackage.getTypesAnnotatedWith(Component.class));
        scan(allClasses);
    }

    @Override
    public void scan(Class<?> baseClass) {
        Reflections r = new Reflections(baseClass);
        Set<Class<?>> allClasses = r.getTypesAnnotatedWith(Component.class);
        scan(allClasses);
    }

    @Override
    public void scan(Class<?> baseClass, String exclusion) {
        Reflections scanPackage = new Reflections(baseClass);
        Reflections exclusionPackage = new Reflections(exclusion);
        Set<Class<?>> allClasses = scanPackage.getTypesAnnotatedWith(Component.class);
        Set<Class<?>> exclusionClasses= exclusionPackage.getTypesAnnotatedWith(Component.class);
        allClasses.removeAll(exclusionClasses);
        scan(allClasses);
    }

    @Override
    public void addBean(String beanName, Object bean) {
        if (NAME_BEANS.containsKey(beanName)) {
            throw new RuntimeException("Bean name already exists , " + beanName);
        }
        NAME_BEANS.put(beanName, bean);
        addBean(bean);
        autowired();
    }

    @Override
    public void addBean(Class<?> inter, Object instance) {
        if (!inter.isInterface()) {
            throw new RuntimeException("Class is not an interface");
        }
        if (INTERFACE_BEANS.containsKey(inter)) {
            throw new RuntimeException("Bean name already exists , " + inter.getName());
        }
        INTERFACE_BEANS.put(inter, instance);
        BEANS.add(instance);
        autowired();
    }

    @Override
    public void addBean(Object bean) {
        TYPE_BEANS.put(bean.getClass(), bean);
        CLASSNAME_BEANS.put(bean.getClass().getName(), bean);
        Class<?>[] interfaces = bean.getClass().getInterfaces();
        for (Class<?> inter : interfaces) {
            //如果这个接口继承了EasyRule 则跳过
            if (EasyRule.class.isAssignableFrom(inter)) continue;
            addBean(inter, bean);
        }
        BEANS.add(bean);
        autowired();
    }

    @Override
    public void updateBean(Class<?> origin, Object proxy) {
        if (!origin.isAnnotationPresent(Component.class)) {
            throw new RuntimeException("Class is not a component");
        }
        String beanName = origin.getAnnotation(Component.class).value();
        if (NAME_BEANS.containsKey(beanName)) {
            NAME_BEANS.put(beanName, proxy);
        }
        if (TYPE_BEANS.containsKey(origin)) {
            TYPE_BEANS.put(origin, proxy);
        }
        if (CLASSNAME_BEANS.containsKey(origin.getName())) {
            CLASSNAME_BEANS.put(origin.getName(), proxy);
        }
        BEANS.add(proxy);
        autowired();
    }

    @Override
    public <T> T getBean(Class<T> c) {
        if (TYPE_BEANS.containsKey(c)) {
            return (T) TYPE_BEANS.get(c);
        }
        for (Class<?> key : TYPE_BEANS.keySet()) {
            if (c.isAssignableFrom(key)) {
                return (T) TYPE_BEANS.get(key);
            }
        }
        if (CLASSNAME_BEANS.containsKey(c.getName())) {
            return (T) CLASSNAME_BEANS.get(c.getName());
        }
        if (INTERFACE_BEANS.containsKey(c)) {
            return (T) INTERFACE_BEANS.get(c);
        }
        return null;
    }

    @Override
    public void reAutowire() {
        autowired();
    }

    @Override
    public Object getBean(String beanName) {
        Object bean = null;
        if (NAME_BEANS.containsKey(beanName)) {
            bean = NAME_BEANS.get(beanName);
        }
        //首字母转大写
        beanName = Character.toUpperCase(beanName.charAt(0)) + beanName.substring(1);
        String finalBeanName = beanName;
        Set<String> beans = CLASSNAME_BEANS.keySet().stream().filter(key -> key.endsWith(finalBeanName)).collect(Collectors.toSet());
        if (beans.size() > 1) {
            //找到了多个bean示例
            throw new RuntimeException("Found multiple beans with name " + beanName);
        }
        if (beans.size() == 1) {
            bean = CLASSNAME_BEANS.get(beans.iterator().next());
        }
        return bean;
    }


    private void instantiateBeans() {
        for (Class<?> component : COMPONENTS) {
            String beanName = component.getAnnotation(Component.class).value();
            if (!beanName.isEmpty()) {
                try {
                    Object bean = component.getConstructor().newInstance();
                    addBean(beanName, bean);
                } catch (InstantiationException | NoSuchMethodException | IllegalAccessException |
                         InvocationTargetException e) {
                    throw new RuntimeException(e);
                }
            } else {
                try {
                    Object bean = component.getConstructor().newInstance();
                    addBean(bean);
                } catch (InstantiationException | NoSuchMethodException | IllegalAccessException |
                         InvocationTargetException e) {
                    throw new RuntimeException(e);
                }
            }
        }


    }

    private void registerBeans() {
        for (Class<?> component : COMPONENTS) {
            List<Method> methods = Arrays.stream(component.getMethods()).filter(m -> m.isAnnotationPresent(Bean.class)).sorted(Comparator.comparingInt(o -> o.getAnnotation(Bean.class).order())).toList();

            for (Method method : methods) {
                String v = method.getAnnotation(Bean.class).value();
                boolean ignoreMethodName = method.getAnnotation(Bean.class).ignoreMethodName();
                Object[] args = new Object[method.getParameterCount()];
                for (int i = 0; i < args.length; i++) {
                    args[i] = getBean(method.getParameterTypes()[i]);
                }
                try {
                    Object bean = method.invoke(getBean(component), args);
                    if (!v.isEmpty()) {
                        addBean(v, bean);
                    } else if (ignoreMethodName) {
                        addBean(bean);
                    } else {
                        addBean(method.getName(), bean);
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private void scan(Set<Class<?>> classes) {
        for (Class<?> c : classes) {

            //不能重复
            if (COMPONENTS.contains(c)) {
                continue;
            }

            if (c.isAnnotationPresent(Component.class)) {
                COMPONENTS.add(c);
            }
        }
    }

    private void autowired() {
        for (Object bean : BEANS) {
            for (Field field : bean.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    try {
                        field.setAccessible(true);
                        field.set(bean, getBean(field.getType()));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
